package server

import (
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"test_chat/ptotocol"
)

type Client struct {
	Name   string
	Conn   net.Conn
	writer *ptotocol.Writer
}

type TcpServer struct {
	listener net.Listener
	clients  []*Client
	mutex    *sync.Mutex
}

var (
	UnknownClient = errors.New("unknown client")
)

func NewTcpServer() *TcpServer {
	return &TcpServer{
		mutex: &sync.Mutex{},
	}
}

func (tcp *TcpServer) Listen(address string) error {
	listener, err := net.Listen("tcp", address)
	if err == nil {
		tcp.listener = listener
	}
	fmt.Printf("listening on %s\n", address)
	return err
}

func (s *TcpServer) Close() {
	s.listener.Close()
}

func (s *TcpServer) accept(conn net.Conn) *Client {
	log.Printf("Accept client from %v,total clients is %v\n", conn.RemoteAddr().String(),
		len(s.clients))
	s.mutex.Lock()
	defer s.mutex.Unlock()
	client := &Client{
		Name:   conn.RemoteAddr().String(),
		writer: ptotocol.NewWriter(conn),
		Conn:   conn,
	}

	s.clients = append(s.clients, client)
	return client

}

func (tcp *TcpServer) Start() {
	for {
		log.Println("start to accept client")
		conn, err := tcp.listener.Accept()
		if err != nil {
			log.Println(err)
		} else {
			if conn != nil {
				log.Println("Start to server client")
				client := tcp.accept(conn)
				go tcp.serve(client)
			} else {
				log.Println("Conn is nil")
			}

		}
	}
}

func (s *TcpServer) remove(client *Client) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	for i, c := range s.clients {
		if c == client {
			s.clients = append(s.clients[:i], s.clients[i+1:]...)
		}
	}

	log.Printf("Cloes connection from %v\n", client.Conn.RemoteAddr().String())
	client.Conn.Close()
}

func (tcp *TcpServer) Broadcast(command interface{}) error {
	for _, client := range tcp.clients {
		client.writer.Write(command)
	}
	return nil
}

func (s *TcpServer) serve(c *Client) {
	cmdRead := ptotocol.NewReader(c.Conn)
	defer s.remove(c)
	for {
		cmd, err := cmdRead.Read()
		if err != nil && err != io.EOF {
			log.Printf("Read error: %v\n", err)
		}
		if cmd == nil {
			switch v := cmd.(type) {
			case ptotocol.SendCmd:
				go s.Broadcast(ptotocol.MessCmd{
					Msg:  v.Msg,
					Name: c.Name,
				})
			case ptotocol.NameCmd:
				c.Name = v.Name
			}
		}
		if err != nil {
			break
		}
	}
}
